﻿using Model;
using HslCommunication;
using HslCommunication.Profinet.Siemens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using Comm;
using Common;
using Server;

namespace PlcServer
{
    public class PlcHelper
    {

        private static PlcHelper plcTag = new PlcHelper();
        /// <summary>
        /// 单例
        /// </summary>
        public static PlcHelper PlcTag
        {
            get => plcTag;
        }

        /// <summary>
        /// 向外推送标签数值事件
        /// </summary>
        public static event Action<string, int> PushTagValEvent;
        /// <summary>
        /// 通知外部  PLC通信状态     做PLC断线发布，para1是PLC编号，para2是通信成功与否
        /// </summary>
        public static event Action<string, bool> MsgConnectionStatus;
        /// <summary>
        /// 向外推送错误信息
        /// </summary>
        public static event Action<string> MsgErrEvent;
        /// <summary>
        /// PLC字典——根据数据库中PLC表动态生成
        /// </summary>
        private static Dictionary<string, SiemensS7Net> PlcDic = new Dictionary<string, SiemensS7Net>();
        /// <summary>
        /// PLC变量字典——和PLC字典关联——通过PLCName关联
        /// </summary>
        private static Dictionary<string, PlcAddr> PlcAddrDic = new Dictionary<string, PlcAddr>();
        /// <summary>
        /// PLC采集采集线程集合——通过PLC表动态生成
        /// </summary>
        private static List<Thread> MonitorThreads;

        private static Dictionary<string, bool> PlcStatus = new Dictionary<string, bool>();
        /// <summary>
        /// 获取PLC轮询的变量数值，如果不存在这个数值，就返回-1,目前只支持 int16型
        /// </summary>	
        public int this[string name]
        {
            get
            {
                if (PlcAddrDic.ContainsKey(name) && PlcStatus[PlcAddrDic[name].PlcNo])
                {
                    return PlcAddrDic[name].AddrValue;
                }
                else
                {
                    return -1;
                }
            }
        }

        /// <summary>
        /// 初始化PLC
        /// </summary>
        /// <returns></returns>
        public static void InitPlc()
        {
            #region 初始化PLC地址字典
            {
                PlcAddrDic.Clear();
                List<PlcAddr> list = new List<PlcAddr>()
                {
                    new PlcAddr()
                    {
                        //No = "Trigger",Address = "DB1.582",AddrValue = -1,IsPara =false,PlcNo = "1#PLC",Note = "触发保存变量",
                        No = "Trigger",Address = "DB1.582",AddrValue = -1,IsPara =false,PlcNo = "1#PLC",Note = "触发保存变量",
                    },
                    new PlcAddr()
                    {
                        //No = "Cmd",Address = "DB1.584",AddrValue = -1,IsPara =false,PlcNo = "1#PLC",Note = "下发命令",
                        No = "CMD",Address = "DB1.584",AddrValue = -1,IsPara =false,PlcNo = "1#PLC",Note = "下发命令",
                    },
                    new PlcAddr()
                    {
                        //No = "BearingNo",Address = "DB1.586",AddrValue = -1,IsPara =false,PlcNo = "1#PLC",Note = "轴承编号",
                        No = "BearingNo",Address = "DB1.586",AddrValue = -1,IsPara =false,PlcNo = "1#PLC",Note = "轴承编号",
                    },
                    new PlcAddr()
                    {
                        //No = "OilNum",Address = "DB1.588",AddrValue = -1,IsPara =true,PlcNo = "1#PLC",Note = "轴承加油量",
                        No = "OilNum",Address = "DB1.588",AddrValue = -1,IsPara =true,PlcNo = "1#PLC",Note = "轴承加油量",
                    },
                };
                //有时候是从数据库获取 PLC变量信息，所以在这个地方要判断下变量是不是null
                if (list == null || list.Count < 1)
                {
                    Logger.WritePlcLog("PLC服务初始过程未检索到PLC变量");
                    return;
                }
                list.ForEach(p =>
                    {
                        PlcAddrDic[p.No] = p;
                        p.PropertyChanged += PlcAddr_PropertyChanged;
                    });
            }

            #endregion

            #region 初始化PLC
            //拿到PLC表  实体集合
            List<Plc> plcs = new List<Plc>();
            List<Plc> totalPlcList = new List<Plc>()
            {
                new Plc(){ID = 1,IP = Syscfg.PlcIp,Note = "主站PLC",No = "1#PLC"}
            };
            //遍历地址标签集合拿到所有的PLC信息
            foreach (var item in PlcAddrDic.Values)
            {
                if (plcs.Count(p => p.No == item.PlcNo) == 0)
                {
                    //从PLC表中获取对应的PLC的具体信息
                    Plc plcTemp = totalPlcList.FirstOrDefault(p => p.No == item.PlcNo);
                    if (plcTemp == null)
                    {
                        Logger.WritePlcLog($"PLC地址【{item.No}】关联到未存在的PLC****检查PLC地址表");
                        continue;
                    }
                    plcs.Add(plcTemp);
                }
            }
            //先把所有的PLC链接停止
            if (PlcDic.Count > 0)
            {
                foreach (var item in PlcDic.Values)
                {
                    item?.ConnectClose();
                }
            }
            PlcDic.Clear(); //清空PLC字典

            if (plcs.Count < 1)
            {
                Msg("未检索到PLC信息，PLC字典空");
                Logger.WritePlcLog("未检索到PLC信息");
                return;
            }
            //PLC配置信息集合建立OK，加入到PLC字典中
            plcs.ForEach(p =>
            {
                try
                {
                    PlcDic.Add(p.No, new SiemensS7Net(SiemensPLCS.S1200, p.IP));
                    PlcStatus.Add(p.No, false);
                }
                catch (Exception ex)
                {
                    Msg("PLC创建失败");
                    Logger.WritePlcLog($"PLC【{p.No}】创建失败");
                    return;
                }
            });
            #endregion

            Start();
        }

        /// <summary>
        /// 向外推送Plc变量信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void PlcAddr_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var addr = sender as PlcAddr;
            if (addr == null || e.PropertyName != "AddrValue") return;

            //if (addr.Enable == false) return;
            if (PushTagValEvent == null)
            {
                return;
            }
            var methodInfo = PushTagValEvent.GetInvocationList();

            foreach (var @delegate in methodInfo)
            {
                var method = (Action<string, int>)@delegate;
                method.BeginInvoke(addr.No, addr.AddrValue, null, null);
            }
        }
        /// <summary>
        /// 开始读取PLC的变量
        /// </summary>
        /// <returns></returns>
        private static bool Start()
        {
            if (MonitorThreads != null && MonitorThreads.Count > 0)
            {
                //说明这是非第一次初始化——先把以前的线程停止
                foreach (var item in MonitorThreads)
                {
                    item.Abort();
                    item.DisableComObjectEagerCleanup();
                }
            }

            MonitorThreads = new List<Thread>();

            foreach (var item in PlcDic)
            {
                MonitorThreads.Add(new Thread(new ThreadStart(() => { MonitorPlc(item.Key); })));
            }

            //把线程库启动
            foreach (var item in MonitorThreads)
            {
                item.IsBackground = true;
                item.Start();
            }
            return true;
        }
        /// <summary>
        /// 轮询PLC变量方法
        /// </summary>
        /// <param name="plcNO">plcNO</param>
        private static void MonitorPlc(string plcNO)
        {
            //连接PLC
         while (!PlcDic[plcNO].ConnectServer().IsSuccess)
            {
                Thread.Sleep(1000);
                Msg($"连接PLC-{plcNO}失败，1s后重试！");
            }
            int[] addr = GetMinMaxAddr(plcNO, out string minadr);
            OperateResult<byte[]> result;
            while (true)
            {
                lock (PlcDic[plcNO])
                {
                    result = PlcDic[plcNO].Read(minadr, (ushort)addr[1]);
                }

                if (result.IsSuccess)
                {
                    foreach (var item in PlcAddrDic)
                    {
                        if (item.Value.PlcNo == plcNO && item.Value.IsPara == false)
                        {
                            //三菱协议按照   字进行读取
                            // item.Value.AddrValue = result.Content[Convert.ToInt16(item.Value.Address.Split('.')[1]) - addr[0]]; //地址前两位是D，要去掉//
                            item.Value.AddrValue = PlcDic[plcNO].ByteTransform.TransInt16(result.Content,
                                Convert.ToInt16(item.Value.Address.Split('.')[1]) - addr[0]);
                        }
                    }
                    MsgConnectionStatus?.Invoke(plcNO, true);
                    PlcStatus[plcNO] = true;
                }
                else
                {
                    MsgConnectionStatus?.Invoke(plcNO, false);
                    PlcStatus[plcNO] = false;
                    Logger.WritePlcLog($"PLC【{plcNO}】通信失败{result.Message}");
                }
                Thread.Sleep(500);
            }
        }
        /// <summary>
        /// 写变量=>只支持Int16型数据
        /// </summary>
        /// <param name="no">地址编号</param>
        /// <param name="val">变量数值</param>
        /// <returns></returns>
        public static OperateResult SetTagInt16(string no, short val)
        {
            //首先看看变量是否在地址表中定义
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.No == no);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要写的变量{no}未绑定PLC，请检查");
                Logger.WritePlcLog($"要写的变量{no}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<bool>(new OperateResult("要写的变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要写的变量未绑定PLC，请检查");
                Logger.WritePlcLog(($"要写的变量{no}未绑定PLC，请检查"));
                return OperateResult.CreateFailedResult<bool>(new OperateResult("要写的变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult result;
            //添加锁，一个PLC   只能有一个写工作
            lock (plc)
            {
                result = plc.Write(Tag.Address.Remove(1, 1), val);
            }
            if (result.IsSuccess)
            {
                string msg = $"写成功【{Tag.Name}】数值【{val}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return OperateResult.CreateSuccessResult();
            }
            else
            {
                string msg = $"写失败【{Tag.Name}】数值【{val}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return result;
            }

        }

        public static OperateResult SetTagFloat(string no, float val)
        {
            //首先看看变量是否在地址表中定义
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.No == no);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要写的变量{no}未绑定PLC，请检查");
                Logger.WritePlcLog($"要写的变量{no}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<bool>(new OperateResult("要写的变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要写的变量未绑定PLC，请检查");
                Logger.WritePlcLog(($"要写的变量{no}未绑定PLC，请检查"));
                return OperateResult.CreateFailedResult<bool>(new OperateResult("要写的变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult result;
            //添加锁，一个PLC   只能有一个写工作
            lock (plc)
            {
                result = plc.Write(Tag.Address.Remove(1, 1), val);
            }
            if (result.IsSuccess)
            {
                string msg = $"写成功【{Tag.Name}】数值【{val}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return OperateResult.CreateSuccessResult();
            }
            else
            {
                string msg = $"写失败【{Tag.Name}】数值【{val}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return result;
            }

        }

        private static void Msg(string msg)
        {
            try
            {
                MsgErrEvent?.Invoke(msg);
            }
            catch (Exception e)
            {
                Logger.WritePlcLog(e.Message, e);
            }
        }
        /// <summary>
        /// 拿到地址中最大和最小的地址
        /// </summary>
        /// <param name="plc">要计算地址的PLC</param>
        /// <returns>返回int[]，其中int[0]时最小地址，int[1]为数据长度</returns>
        private static int[] GetMinMaxAddr(string plcNO, out string minAddrstr)
        {
            int[] val = { 9999, -1 };//[0]=min，[1]=length
            string adr = "";
            int maxAddr = PlcAddrDic.Values.Max(p =>
            {
                if (p.PlcNo == plcNO && !p.IsPara)
                {
                    return Convert.ToInt32(p.Address.Split('.')[1]);
                }
                return 0;
            });

            int minAddr = PlcAddrDic.Values.Min(p =>
            {
                if (p.PlcNo == plcNO && !p.IsPara)
                {
                    adr = p.Address;
                    return Convert.ToInt32(p.Address.Split('.')[1]);
                }
                return 99999;
            });
            minAddrstr = PlcAddrDic.Values.FirstOrDefault(p => p.PlcNo == plcNO && p.IsPara == false)?.Address.Split('.')[0] + '.' + minAddr.ToString();
            val[0] = minAddr;
            //西门子协议   按照字节读取，读取的地址要加2
            val[1] = maxAddr - minAddr + 2;
            return val;
        }
        /// <summary>
        /// 根据标签的NO拿到地址的数值
        /// </summary>
        /// <param name="tagNo"></param>
        /// <returns></returns>
        public static OperateResult<int> GetTagValInt(string tagNo)
        {
            if (!PlcAddrDic.ContainsKey(tagNo))
            {
                return OperateResult.CreateFailedResult<int>(new OperateResult("地址列表中不包含这个地址"));
            }
            string NO = "";
            NO = PlcAddrDic[tagNo].PlcNo;
            if (!PlcStatus[NO])
            {
                var result = OperateResult.CreateFailedResult<int>(new OperateResult());
                result.Message = "这个地址标签的PLC通信状态失败";
                return result;
            }
            return OperateResult.CreateSuccessResult<int>(PlcAddrDic[tagNo].AddrValue);
        }
        /// <summary>
        /// 拿到PLC地址集合
        /// </summary>
        /// <returns></returns>
        public static List<PlcAddr> GetPlcAddrs()
        {
            return PlcAddrDic.Values.ToList();
        }
        /// <summary>
        /// 获取PLC最新数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tagNo"></param>
        /// <returns></returns>
        public static OperateResult<byte[]> GetValInTime(string no, ushort length)
        {
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.No == no);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要读取变量{no}未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{no}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<byte[]>(new OperateResult("要读取变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要读取的变量未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{no}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<byte[]>(new OperateResult("要读取变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult<byte[]> result;

            lock (plc)
            {
                result = plc.Read(Tag.Address, length);
            }
            if (result.IsSuccess)
            {
                return OperateResult.CreateSuccessResult(result.Content);
            }
            else
            {
                string msg = $"读取失败【{Tag.Name}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return OperateResult.CreateFailedResult<byte[]>(result);
            }
        }
        /// <summary>
        /// 立即读取变量=>float类型数据
        /// </summary>
        /// <param name="no"></param>
        /// <returns></returns>
        public static OperateResult<float> GetValFloatInTime(string no)
        {
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.No == no);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要读取变量{no}未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{no}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<float>(new OperateResult("要读取变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要读取的变量未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{no}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<float>(new OperateResult("要读取变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult<float> result;

            lock (plc)
            {
                result = plc.ReadFloat(Tag.Address);
            }
            if (result.IsSuccess)
            {
                return OperateResult.CreateSuccessResult(result.Content);
            }
            else
            {
                string msg = $"读取失败【{Tag.Name}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return OperateResult.CreateFailedResult<float>(result);
            }
        }
        /// <summary>
        /// 立即读取变量=>字节
        /// </summary>
        /// <param name="name">变量名称</param>
        /// <param name="length">数据长度</param>
        /// <returns></returns>
        public static OperateResult<byte[]> GetValBytes(string name, ushort length)
        {
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.Name == name);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要读取变量{name}未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<byte[]>(new OperateResult("要读取变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要读取的变量未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<byte[]>(new OperateResult("要读取变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult<byte[]> result;

            lock (plc)
            {
                result = plc.Read(Tag.Address, length);
            }
            if (result.IsSuccess)
            {
                return OperateResult.CreateSuccessResult(result.Content);
            }
            else
            {
                string msg = $"读取失败【{Tag.Name}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return OperateResult.CreateFailedResult<byte[]>(result);
            }
        }
        /// <summary>
        /// 从字节数组中解析出字符串
        /// </summary>
        /// <param name="bytes">要解析的字节数组</param>
        /// <param name="offs">偏移量</param>
        /// <returns></returns>
        public static string GetInfoStr(byte[] bytes, int offs)
        {

            if (bytes.Length < 1)
            {
                return "";
            }

            var str = PlcDic?.Values?.FirstOrDefault()?.ByteTransform.TransString(bytes, offs + 2, (int)bytes[offs + 1], Encoding.UTF8);
            return str;
        }
        /// <summary>
        /// 从字节数组中获取浮点数
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <param name="offs">偏移量</param>
        /// <returns></returns>
        public static float GetInfoFloat(byte[] bytes, int offs)
        {

            if (bytes.Length < 1)
            {
                return 0.0F;
            }

            var str = PlcDic?.Values?.FirstOrDefault()?.ByteTransform.TransSingle(bytes, offs);
            return str ?? 0.0f;
        }
    }
    /// <summary>
    /// PLC变量Model
    /// </summary>
    public class PlcAddr : INotifyPropertyChanged
    {
        private int _ID;
        private string _NO = "";
        private string _Name = "";
        private string _Address = "";
        private string _Note = "";
        private string _plcNo;
        private int _AddrValue = -1;
        private DateTime _dateTimeSpan = DateTime.Now;
        private bool _enable = true;
        private bool _isPara;
        private string _addrtype;

        public int ID { get => _ID; set => Set(ref _ID, value); }


        /// <summary>
        /// 编号
        /// </summary>
        public string No { get => _NO; set => Set(ref _NO, value); }

        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get => _Name; set => Set(ref _Name, value); }

        /// <summary>
        /// 地址
        /// </summary>
        public string Address { get => _Address; set => Set(ref _Address, value); }

        /// <summary>
        /// 备注
        /// </summary>
        public string Note { get => _Note; set => Set(ref _Note, value); }

        /// <summary>
        /// 地址读取值
        /// </summary>
        public int AddrValue
        {
            get => _AddrValue;
            set => Set(ref _AddrValue, value);
        }
        /// <summary>
        /// 对应PLC的ID
        /// </summary>
        public string PlcNo { get => _plcNo; set => Set(ref _plcNo, value); }
        /// <summary>
        /// 建立时间
        /// </summary>
        public DateTime TimeStamp { get => _dateTimeSpan; set => Set(ref _dateTimeSpan, value); }
        /// <summary>
        /// 是否是参数，是参数不参与轮询，不是参数，参与轮询
        /// </summary>
        public bool IsPara
        {
            get => _isPara;
            set => Set(ref _isPara, value);
        }

        /// <summary>
        /// 是否启用
        /// </summary>
        public bool Enable
        {
            get => _enable;
            set => Set(ref _enable, value);
        }
        public event PropertyChangedEventHandler PropertyChanged;

        private void Set<T>(ref T val, T newval, [CallerMemberName] string name = "")
        {
            if (!EqualityComparer<T>.Default.Equals(val, newval))
            {
                val = newval;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            }
        }
    }
    /// <summary>
    /// PLC通信Model
    /// </summary>
    public class PlcInfo
    {
        private int _ID;
        private string _NO = "";
        private string _Name = "";
        private string _IP;
        private int _Port;
        private string _Note = "";

        public int ID { get => _ID; set => Set(ref _ID, value); }
        /// <summary>
        /// 编号
        /// </summary>
        public string No { get => _NO; set => Set(ref _NO, value); }

        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get => _Name; set => Set(ref _Name, value); }

        /// <summary>
        /// IP地址
        /// </summary>
        public string IP { get => _IP; set => Set(ref _IP, value); }

        /// <summary>
        /// 端口
        /// </summary>
        public int Port { get => _Port; set => Set(ref _Port, value); }

        /// <summary>
        /// 备注
        /// </summary>
        public string Note { get => _Note; set => Set(ref _Note, value); }
        private void Set<T>(ref T val, T newval)
        {
            if (!EqualityComparer<T>.Default.Equals(val, newval))
            {
                val = newval;
            }
        }
    }
}
